package edu.stanford.bmir.protege.web.client.ui.ontology.stkos.mapper.common;

import java.util.Collection;
import java.util.List;

import com.google.common.collect.Lists;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.gwtext.client.core.EventObject;
import com.gwtext.client.core.UrlParam;
import com.gwtext.client.data.ArrayReader;
import com.gwtext.client.data.FieldDef;
import com.gwtext.client.data.ObjectFieldDef;
import com.gwtext.client.data.Record;
import com.gwtext.client.data.RecordDef;
import com.gwtext.client.data.Store;
import com.gwtext.client.data.StringFieldDef;
import com.gwtext.client.widgets.Button;
import com.gwtext.client.widgets.PagingToolbar;
import com.gwtext.client.widgets.Toolbar;
import com.gwtext.client.widgets.Window;
import com.gwtext.client.widgets.event.ButtonListenerAdapter;
import com.gwtext.client.widgets.form.Field;
import com.gwtext.client.widgets.form.FormPanel;
import com.gwtext.client.widgets.form.TextField;
import com.gwtext.client.widgets.form.event.TextFieldListenerAdapter;
import com.gwtext.client.widgets.grid.ColumnConfig;
import com.gwtext.client.widgets.grid.ColumnModel;
import com.gwtext.client.widgets.grid.GridPanel;
import com.gwtext.client.widgets.grid.event.GridRowListenerAdapter;
import com.gwtext.client.widgets.layout.AnchorLayoutData;
import com.gwtext.client.widgets.layout.FitLayout;

import edu.stanford.bmir.protege.web.client.rpc.data.EntityData;
import edu.stanford.bmir.protege.web.client.rpc.stkosutil.StkosUtilServiceManager;
import edu.stanford.bmir.protege.web.client.rpc.stkosutil.common.SearchCondition;
import edu.stanford.bmir.protege.web.client.rpc.stkosutil.common.UtilEntityType;
import edu.stanford.bmir.protege.web.client.ui.library.msgbox.MessageBox;
import edu.stanford.bmir.protege.web.client.ui.ontology.stkos.common.interfaces.StkosTreeNodeHighlightable;
import edu.stanford.bmir.protege.web.client.ui.util.GWTProxy;
import edu.stanford.bmir.protege.web.client.ui.util.PaginationUtil;
import edu.stanford.bmir.protege.web.shared.project.ProjectId;

public class StkosMappingNoSearchUtil {

	public final static int pageSize = 20;

	public static final ArrayReader reader = new ArrayReader(new RecordDef(
			new FieldDef[] { new ObjectFieldDef("entity"),
					new StringFieldDef("browserText") }));

	private StkosMappingNoSearchProxy proxy;

	private GridPanel searchGrid;

	private String projectName;
	
	private String refProjectName;

	private UtilEntityType searchedValueType;

	private StkosTreeNodeHighlightable highlightable;

	public StkosMappingNoSearchUtil(String projectName,String refProjectName,
			StkosTreeNodeHighlightable highlightable) {
		this(projectName, refProjectName,highlightable, null);
	}

	public StkosMappingNoSearchUtil(String projectName,String refProjectName,
			StkosTreeNodeHighlightable highlightable,
			UtilEntityType searchedValueType) {
		this.projectName = projectName;
		this.refProjectName = refProjectName;
		this.highlightable = highlightable;
		this.searchedValueType = searchedValueType;
		init();
	}

	protected void init() {
		proxy = new StkosMappingNoSearchProxy();

		searchGrid = new GridPanel();

		searchGrid.addGridRowListener(new GridRowListenerAdapter() {
			@Override
			public void onRowDblClick(GridPanel grid, int rowIndex,
					EventObject e) {
				highlightInTree();
			}
		});

		ColumnConfig browserTextCol = new ColumnConfig();
		browserTextCol.setHeader("Results");
		browserTextCol.setId("browserText");
		browserTextCol.setDataIndex("browserText");
		browserTextCol.setResizable(true);
		browserTextCol.setSortable(true);
		browserTextCol
				.setTooltip("Double click search result to select in tree.");

		ColumnConfig[] columns = new ColumnConfig[] { browserTextCol };

		ColumnModel columnModel = new ColumnModel(columns);
		searchGrid.setColumnModel(columnModel);

		Store store = new Store(proxy, reader);

		searchGrid.setStore(store);

		PagingToolbar pToolbar = PaginationUtil.getNewPagingToolbar(store,
				pageSize);
		searchGrid.setBottomToolbar(pToolbar);

		Toolbar toolbar = new Toolbar();

		TextField searchField = new TextField("Search: ", "search");
		searchField.setValidateOnBlur(false);
		searchField.setSelectOnFocus(true);
		searchField.setWidth(400);
		searchField.setEmptyText("Type search string ...");
		searchField.addListener(new TextFieldListenerAdapter() {
			@Override
			public void onSpecialKey(final Field field, final EventObject e) {
				if (e.getKey() == EventObject.ENTER) {
					searchFieldValid(field.getValueAsString());
				}
			}

			@Override
			public void onValid(Field field) {
				searchFieldValid(field.getValueAsString());
			}
		});

		toolbar.addText("<i>Search</i>:&nbsp&nbsp");
		toolbar.addElement(searchField.getElement());

		searchGrid.setTopToolbar(toolbar);

		searchGrid.setStripeRows(true);
		searchGrid.setAutoExpandColumn("browserText");
		searchGrid.setBorder(false);
	}

	protected void searchFieldValid(String value) {
		Integer mappingNo = null;
		try {
			mappingNo = Integer.parseInt(value);
		} catch (Exception ex) {
			GWT.log("Getting search string error.", ex);
		}
		reload(mappingNo);
	}

	protected void reload(Integer mappingNo) {
		searchGrid.getStore().removeAll();

		if (mappingNo == null) {
			MessageBox.showAlert("Warning", "MappingNo is invalid.");
			return;
		}

		AsyncCallback<List<EntityData>> callback = new AsyncCallback<List<EntityData>>() {

			@Override
			public void onFailure(Throwable caught) {
				GWT.log("Search mappingNo error", caught);
				MessageBox
						.showAlert("Search error",
								"An error occurred during the search. Please try again.");
			}

			@Override
			public void onSuccess(List<EntityData> result) {
				proxy.setResults(result);
				searchGrid.getStore().load(0, pageSize);
			}
		};

		executeSearch(mappingNo, callback);
	}

	public void search(Integer mappingNo) {
		searchGrid.getStore().removeAll();

		AsyncCallback<List<EntityData>> callback = new AsyncCallback<List<EntityData>>() {

			@Override
			public void onFailure(Throwable caught) {
				GWT.log("Search mappingNo error", caught);
				MessageBox
						.showAlert("Search error",
								"An error occurred during the search. Please try again.");
			}

			@Override
			public void onSuccess(List<EntityData> result) {
				if (result == null || result.size() == 0) {
					MessageBox.showAlert("Search info",
							"Search end,No records to display.");
				} else if (result.size() == 1) {
					highlightInTree(result);
				} else {
					proxy.setResults(result);
					searchGrid.getStore().load(0, pageSize);
					showSearchResults();
				}
			}
		};

		executeSearch(mappingNo, callback);
	}

	protected void executeSearch(Integer mappingNo,
			AsyncCallback<List<EntityData>> callback) {
		if(callback==null){
			GWT.log("Matching mappingNumber error", new NullPointerException("The required argument callback can not be null."));
			return;
		}
		if (mappingNo == null) {
			callback.onFailure(new NullPointerException("The required argument mappingNo can not be null."));
			return;
		}
		if (projectName == null || refProjectName == null) {
			callback.onFailure(new NullPointerException("The required argument projectId can not be null."));
			return;
		}

		if (searchedValueType == null) {
			searchedValueType = UtilEntityType.ANY_ENTITY;
		}

		SearchCondition searchCondition = new SearchCondition();
		searchCondition.setRefProjectName(refProjectName);
		searchCondition.setEntityType(searchedValueType);
		searchCondition.setMappingNo(mappingNo);

		StkosUtilServiceManager.getInstance().search(projectName,searchCondition, callback);
	}

	protected void showSearchResults() {
		final Window window = new Window();
		window.setTitle("Search results");
		window.setWidth(500);
		window.setHeight(365);
		window.setLayout(new FitLayout());

		FormPanel panel = new FormPanel();

		Button showInTreeButton = new Button("Select in tree",
				new ButtonListenerAdapter() {
					@Override
					public void onClick(Button button, EventObject e) {
						highlightInTree();
					}
				});

		Button closeButton = new Button("Close", new ButtonListenerAdapter() {
			@Override
			public void onClick(Button button, EventObject e) {
				window.close();
				window.destroy();
			}
		});

		panel.add(searchGrid, new AnchorLayoutData("100% 100%"));
		panel.addButton(showInTreeButton);
		panel.addButton(closeButton);

		window.add(panel);
		window.show();
	}

	protected Collection<EntityData> getSelections() {
		if (searchGrid == null) {
			return null;
		}
		Record[] records = searchGrid.getSelectionModel().getSelections();
		if (records == null || records.length == 0) {
			return null;
		}
		Collection<EntityData> selections = Lists.newArrayList();
		for (Record record : records) {
			try {
				selections.add((EntityData) record.getAsObject("entity"));
			} catch (Exception e) {
				GWT.log("Get mappingNo grid selected items error.", e);
			}
		}
		return selections;
	}

	protected void highlightInTree(Collection<EntityData> selections) {
		if (selections == null || selections.isEmpty()) {
			MessageBox.showAlert("Warning", "Can not get required data.");
			return;
		}
		if (highlightable == null) {
			return;
		}

		highlightable.setHighlight(selections);
	}

	protected void highlightInTree() {
		highlightInTree(getSelections());
	}

	protected class StkosMappingNoSearchProxy extends GWTProxy {

		private List<EntityData> results;

		@Override
		public void load(int start, int limit, String sort, String dir,
				final JavaScriptObject o, UrlParam[] baseParams) {
			if (results == null || results.size() == 0) {
				loadResponse(o, false, 0, (JavaScriptObject) null);
			} else {
				loadResponse(o, true, results.size(), getRecords(results));
			}
		}

		private Object[][] getRecords(List<EntityData> result) {
			Object[][] resultAsObjects = new Object[result.size()][2];

			int i = 0;
			for (EntityData ed : result) {
				Object[] obj = new Object[] { ed, ed.getBrowserText() };
				resultAsObjects[i++] = obj;
			}
			return resultAsObjects;
		}

		public void setResults(List<EntityData> results) {
			this.results = results;
		}
	}
}
